﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using YAPL.Language.Compiler.CodeGen.ReflectionEmitGenerator.AstGen.Helpers;
using YAPL.Language.Core;
using YAPL.Language.Core.Exceptions;
using YAPL.Language.Core.Types;
using Ast = YAPL.Language.Ast;
using YAPL.Util;

namespace YAPL.Language.Compiler.CodeGen.ReflectionEmitGenerator.AstGen {
	class ReProgramDefGenerator : ReExpressionGenerator {
		private const string MAIN_CLASS_NAME = "Program";
		private const string ENTRY_POINT_NAME = "Main";
		private const string MAIN_FUNCTION_NAME = "MainFunc";
		public ReProgramDefGenerator(ReCodeGenerator codeGenerator) : base(codeGenerator) {}
		public override void Generate(Ast.Expression expr) {
			var programDefExpression = (Ast.ProgramDefExpression) expr;
			var assemblyBuilder = Generator.Get<AssemblyBuilder>(ReCodeGenerator.ASSEMBLY_BUILDER_KEY);
			var moduleBuilder = Generator.Get<ModuleBuilder>(ReCodeGenerator.MODULE_BUILDER_KEY);
			var mainTypeBuilder = moduleBuilder.DefineType(MAIN_CLASS_NAME);

			//Emit program entry point
			var mainFuncBuilder = mainTypeBuilder.DefineMethod(ENTRY_POINT_NAME,
				MethodAttributes.Static | MethodAttributes.Public, typeof (void), Type.EmptyTypes);

			//Emit main function body
			var ilGen = mainFuncBuilder.GetILGenerator();

			var endLabel = ilGen.DefineLabel();
			
			//surround main code with try/catch
			//ilGen.BeginExceptionBlock();

			//create functions
			//Generator.FunctionDefClasses.Each(function => ilGen.emitCreateFunction(function));

			Generator.Set<ILGenerator>(ReCodeGenerator.IL_GENERATOR_KEY, ilGen);
			//Emit main function definition
			var mainFuncDef = GenerateMainFunction(programDefExpression.ProgramBody);
			Generator.EmitExpression(mainFuncDef);


			//call main function
			ilGen.DeclareLocal(typeof (Scope)); //scope for main function call
			ilGen.EmitGetFunctionFromGCL(MAIN_FUNCTION_NAME, ClassNames.GlobalScope);
			var scopeVar = ilGen.EmitCreateAndSaveScope();
			ilGen.EmitFunctionCall(scopeVar);
			ilGen.Emit(OpCodes.Pop);
			ilGen.Emit(OpCodes.Leave_S, endLabel);

			/*
			//catch exceptions
			var getMessageMethod = typeof (Exception).GetProperty("Message").GetGetMethod();
			var printExceptionMethod = typeof(YAPLException).GetMethod("PrintRuntimeExceptionMessage", new[] { typeof(string)});
			ilGen.BeginCatchBlock(typeof (Exception));
			ilGen.Emit(OpCodes.Callvirt,getMessageMethod);
			ilGen.Emit(OpCodes.Call, printExceptionMethod);
			ilGen.Emit(OpCodes.Leave_S, endLabel);
			ilGen.EndExceptionBlock();*/

			//program ends
			ilGen.MarkLabel(endLabel);
			ilGen.Emit(OpCodes.Ret);

			mainTypeBuilder.CreateType();
			moduleBuilder.CreateGlobalFunctions();
			assemblyBuilder.SetEntryPoint(mainFuncBuilder);
		}

		private static Ast.FunctionDefExpression GenerateMainFunction(Ast.CodeBlockExpression programBody) {
			var mainFuncParams = new ReadOnlyCollection<Ast.ParameterExpression>(new List<Ast.ParameterExpression>());
			var mainFuncDefaults = new ReadOnlyCollection<Ast.Expression>(new List<Ast.Expression>());
			return Ast.Expressions.Function(MAIN_FUNCTION_NAME, mainFuncParams, programBody);
		}

	}
}
